package it.tukano.odt;

import it.tukano.collections.MapExt;
import it.tukano.fun.Function1;
import it.tukano.fun.Tuple2;
import it.tukano.io.FileExt;
import it.tukano.io.MemBuffer;
import it.tukano.io.Zipper;
import it.tukano.jpgi.test.SvgContainer;
import it.tukano.lang.StringExt;
import it.tukano.reflection.MethodExt;
import it.tukano.swingdeco.ImageIconExt;
import it.tukano.swingdeco.containers.JPanelExt;
import it.tukano.swingdeco.containers.JRowPanel;
import it.tukano.swingdeco.containers.JToolBarExt;
import it.tukano.swingdeco.controls.JButtonExt;
import it.tukano.xml.XmlDoc;
import it.tukano.xml.XmlDoc.XmlNode;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Desktop;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;

/**
 * A form that uses an odt document as report view/print/preview/store. The
 * OdtForm requires a odt file as base and an xml document, named "odt file
 * name.form.xml" in the same folder. Optionally, it can use a svg document as
 * layout (named "odt file name.layout.svg", in the same dir). The odt document
 * contains the name and positions of the field, in the form: {name of the
 * field}. The .form.xml file defines the fields' properties (size, order,
 * type...). The optional .layout.svg file defines the bounds of the fields and
 * the background of the form. The svg document must define one rect element
 * with id = name of the field for each field in the .form.xml document
 * (otherwise the missing fields will not be displayed).
 *
 * @author pgi
 */
public class OdtForm {

    private final FileExt file;
    private final ArrayList<OdtField> fields = new ArrayList<OdtField>();
    private final List<Function1<String, String>> pretransformers = new LinkedList<Function1<String, String>>();
    private OdtFormComponent component;
    private final int ROW_SIZE;
    private final SvgContainer svgContainer;
    private final FileExt layoutFile;
    private boolean enableFormLoadOnDrop = true;
    private Properties properties = null;

    public OdtForm(String odtFilePath) {
        this(new FileExt(odtFilePath));
    }

    public void setProperty(String name, String value) {
        if (properties == null) {
            properties = new Properties();
        }
        properties.setProperty(name, value);
    }

    public String getProperty(String name) {
        if (properties == null) {
            return null;
        } else {
            return properties.getProperty(name);
        }
    }

    public OdtForm(FileExt file) {
        this(file, null);
    }

    public OdtForm(String path, Function1<XmlNode, OdtField> fieldGenerator) {
        this(new FileExt(path), fieldGenerator);
    }

    public OdtForm(FileExt file, Function1<XmlNode, OdtField> fieldGenerator) {
        this.ROW_SIZE = 50;
        this.file = file;
        File xmlStructure = file.replaceExtension(".form.xml").getFile();
        if (xmlStructure.exists()) {
            XmlDoc formFieldsList = XmlDoc.newInstance(file.replaceExtension(".form.xml").getFile());
            for (XmlNode xmlNode : formFieldsList.getRoot().listChildren("field", true)) {
                OdtField f = null;
                if (fieldGenerator != null) {
                    f = fieldGenerator.apply(xmlNode);
                }
                if (f == null) {
                    f = new OdtField(xmlNode);
                }
                fields.add(f);
            }
        }
        Collections.sort(fields, new Comparator<OdtField>() {

            public int compare(OdtField o1, OdtField o2) {
                return Integer.signum(o1.getIndex() - o2.getIndex());
            }
        });
        layoutFile = file.replaceExtension(".layout.svg");
        if (layoutFile.getFile().exists()) {
            svgContainer = new SvgContainer(layoutFile.getURL().toString());//SvgLayoutManager(layoutFile.getFile());
        } else {
            svgContainer = null;
        }
    }

    public OdtField getField(String fieldName) {
        for (OdtField odtField : fields) {
            if (odtField.getName().equals(fieldName)) {
                return odtField;
            }
        }
        return null;
    }

    public OdtFormComponent getFormComponent() {
        if (component == null) {
            component = createFormComponent(ROW_SIZE);
        }
        return component;
    }

    public void acquireDataFromOdtDocument(FileExt file) {
        Zipper zipper = new Zipper(file);
        String meta = zipper.getEntryAsText("meta.xml", "utf-8");
        XmlDoc doc = XmlDoc.newInstanceFromXml(meta);
        XmlNode dataHolder = doc.getRoot().getFirstChild("office:meta").getFirstChild("meta:user-defined");
        if (dataHolder != null && dataHolder.hasAttributeValue("meta:name", "formdata")) {
            String hex = dataHolder.getContentAsString();
            MemBuffer buffer = MemBuffer.newInstanceFromHexBytes(hex);
            acquireDataFromSerializedMap(buffer.getInputStream());
        } else {
            Logger.getLogger(OdtForm.class.getName()).log(Level.INFO, "no formdata int document " + file);
        }
    }

    protected void acquireDataFromSerializedMap(InputStream in) {
        try {
            ObjectInputStream oin = new ObjectInputStream(in);
            Map<?, ?> map = Map.class.cast(oin.readObject());
            oin.close();
            acquireDataFromMap(map);
        } catch (Exception ex) {
            Logger.getLogger(OdtForm.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected void acquireDataFromMap(Map<?, ?> map) {
        for (Map.Entry<? extends Object, ? extends Object> entry : map.entrySet()) {
            String fieldName = String.valueOf(entry.getKey());
            StringExt fieldValue = StringExt.wrap(entry.getValue());
            OdtField field = getField(fieldName);
            if (field != null) {
                field.getEditor().setValue(fieldValue);
            }
        }
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                OdtForm form = new OdtForm(FileExt.newUserFolderFile("Documents/form_fattura.odt"));
                JFrame window = new JFrame("Test");
                window.add(form.getFormComponent());
                window.setSize(640, 480);
                window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                window.setVisible(true);
            }
        });
    }

    public void onSaveForm() {
    }

    public void onPreviewForm() {
        if (!checkFields(fields)) {
            return;
        }

        FileExt temp = generateTempDocument();
        if (temp != null) {
            try {
                Desktop.getDesktop().open(temp.getFile());
            } catch (IOException ex) {
                Logger.getLogger(OdtForm.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void displayExceptionMessage(Exception ex) {
        JOptionPane.showMessageDialog(null, ex);
    }

    public void onNewForm() {
        int op = JOptionPane.showConfirmDialog(component, "Cancellare il contenuto dei campi?", "Conferma", JOptionPane.YES_NO_OPTION);
        if (op == JOptionPane.YES_OPTION) {
            for (OdtField field : fields) {
                field.clearEditedValue();
            }
        }
    }

    public void onPrintForm() {
        if (!checkFields(fields)) {
            return;
        }
        FileExt temp = generateTempDocument();
        if (temp != null) {
            try {
                Desktop.getDesktop().print(temp.getFile());
            } catch (IOException ex) {
                Logger.getLogger(OdtForm.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public File createTemporaryFile() {
        String name = file.getFile().getName();
        String home = System.getProperty("user.home");
        File temp = new File(home, name);
        int index = 0;
        while (temp.exists()) {
            temp = new File(home, index + name);
            index++;
        }
        temp.deleteOnExit();
        return temp;
    }

    public void addPreTransformer(Function1<String, String> xmlToXmlTransform) {
        pretransformers.add(xmlToXmlTransform);
    }

    public void clearPreTransformers() {
        pretransformers.clear();
    }

    public String preTransformXml(String xml) {
        for (Function1<String, String> function1 : pretransformers) {
            xml = function1.apply(xml);
        }
        return xml;
    }

    public FileExt generateTempDocument() {
        return generateTempDocument(null);
    }
    
    public FileExt generateTempDocument(String filename) {
        String meta = getOdtMetaXml();
        String data = getFormFieldValueHexBuffer();
        XmlDoc doc = XmlDoc.newInstanceFromXml(meta);
        XmlNode parent = doc.getRoot().getFirstChild("office:meta");
        XmlNode child = parent.createChild("meta:user-defined");
        child.setAttribute("meta:name", "formdata");
        child.setTextContent(data);
        meta = doc.toXmlString();

        String xml = preTransformXml(getOdtContentXml());
        for (OdtField field : fields) {
            xml = applyFieldTransform(field, xml);
        }
        Zipper zipper = new Zipper(file);
        try {
            File temp = createTemporaryFile();
            MapExt<String, byte[]> map = MapExt.newHashMap();
            map.push("content.xml", xml.getBytes("utf-8"));
            map.push("meta.xml", meta.getBytes("utf-8"));
            //zipper.replace(temp, "content.xml", xml.getBytes("utf-8"));
            zipper.replace(temp, map);
            return new FileExt(temp);
        } catch (IOException ex) {
            Logger.getLogger(OdtForm.class.getName()).log(Level.INFO, "temp error", ex);
        }
        return null;
    }

    protected String getFormFieldValueHexBuffer() {
        String hex = null;
        try {
            MemBuffer buffer = MemBuffer.newInstance();
            Map<String, String> formFieldValues = getFormFieldValues();
            ObjectOutputStream out = new ObjectOutputStream(buffer.getOutputStream());
            out.writeObject(formFieldValues);
            out.close();
            hex = buffer.getHexString();
        } catch (IOException ex) {
            Logger.getLogger(OdtForm.class.getName()).log(Level.SEVERE, "error", ex);
        }
        return hex;
    }

    protected Map<String, String> getFormFieldValues() {
        Map<String, String> map = new HashMap<String, String>();
        for (OdtField f : fields) {
            map.put(f.getName(), f.getEditor().getEditorValue().toString());
        }
        return map;
    }

    protected String applyFieldTransform(OdtField field, String xml) {
//        if(field.getEditor() instanceof OdtOpListItemEditor) {
//            return applyOpListItemTransform(field, xml);
//        } else {
        Tuple2<StringExt, String> editorValue = field.getEditor().applyEditorValue(field, xml);
        StringExt value = editorValue.getA();
        xml = editorValue.getB();
        String text = value == null ? "" : value.toString();
        if (text == null) {
            text = "";
        }
        switch (field.getCase()) {
            case lower:
                text = text.toLowerCase();
                break;
            case upper:
                text = text.toUpperCase();
                break;
            case name:
                if (text.length() > 0) {
                    text = text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
                }
                break;
            case none:
            default:
                break;
        }
        xml = xml.replace(field.getToken(), text);
        xml = xml.replace(field.getToken().toLowerCase(), text);
        xml = xml.replace(field.getToken().toUpperCase(), text);
        return xml;
//        }
    }

    /*
     * field with type oplistitem. If the field is checked, remove the token of
     * the field if the field is not checked, remove the entire list item from
     * the content
     */
    protected String applyOpListItemTransform(OdtField field, String xml) {
        OdtOpListItemEditor editor = (OdtOpListItemEditor) field.getEditor();
        if (editor.isSelected()) {
            xml = xml.replace(field.getToken(), "");
        } else {
            int index = xml.indexOf(field.getToken());
            int startIndex = 0;
            StringBuilder buffer = new StringBuilder();
            int lim = 50;
            for (int i = index; i >= 0 && (--lim >= 0); i--) {
                buffer.insert(0, xml.charAt(i));
                if (buffer.indexOf("<text:list-item>") == 0) {
                    startIndex = i;
                    break;
                }
            }
            if (startIndex != 0) {
                String endToken = "</text:list-item>";
                int endIndex = xml.indexOf(endToken, startIndex);
                if (endIndex >= 0) {
                    xml = xml.substring(0, startIndex) + xml.substring(endIndex + endToken.length());
                }
            }
        }
        return xml;
    }

    public OdtForm appendDefaultCloseButton(Runnable onAction) {
        JButtonExt close = new JButtonExt(new ImageIconExt("toolbarButtonGraphics.navigation", "Back24.gif")).onActionPerformed(onAction);
        getFormComponent().getToolbar().add(close, 0);
        return this;
    }

    public OdtFormComponent createFormComponent(int MAX_ROW_SIZE) {
        JButtonExt saveForm = new JButtonExt(new ImageIconExt("toolbarButtonGraphics.general", "Save24.gif")).onActionPerformed((Runnable) new MethodExt(this, "onSaveForm"));
        JButtonExt previewForm = new JButtonExt(new ImageIconExt("toolbarButtonGraphics.general", "PrintPreview24.gif")).onActionPerformed((Runnable) new MethodExt(this, "onPreviewForm"));
        JButtonExt newForm = new JButtonExt(new ImageIconExt("toolbarButtonGraphics.general", "New24.gif")).onActionPerformed((Runnable) new MethodExt(this, "onNewForm"));
        JButtonExt printForm = new JButtonExt(new ImageIconExt("toolbarButtonGraphics.general", "Print24.gif")).onActionPerformed((Runnable) new MethodExt(this, "onPrintForm"));
        JToolBarExt toolbar = new JToolBarExt().append(newForm, previewForm, printForm, saveForm).floatable(false);
        OdtFormComponent panel = new OdtFormComponent();
        panel.add(new JScrollPane(createEditorPanel(fields, MAX_ROW_SIZE)));
        panel.add(toolbar, BorderLayout.NORTH);
        panel.setToolbar(toolbar);
        return panel;
    }

    public Component createEditorPanel(Iterable<OdtField> fields, int maxRowSize) {
        if (svgContainer != null) {
            return createSvgPanel(fields, svgContainer, layoutFile);
        } else {
            return createRowPanel(fields, maxRowSize);
        }
    }

    public Component createSvgPanel(Iterable<OdtField> fields, SvgContainer panel, FileExt layoutFile) {
        for (OdtField field : fields) {
            OdtFieldEditor editor = field.getEditor();
            editor.hideDefaultLabel();
            editor.setName(field.getName());
            panel.add(editor);
        }
        panel.revalidate();
        return panel;
    }

    public Component createRowPanel(Iterable<OdtField> fields, int maxRowSize) {
        JRowPanel rowPanel = new JRowPanel();
        for (OdtField field : fields) {
            OdtFieldEditor editor = field.getEditor();
            rowPanel.append(maxRowSize, field.getSize(), new JPanelExt(new BorderLayout()).append(editor, BorderLayout.NORTH));
        }
        return rowPanel;
    }

    public String getOdtContentXml() {
        Zipper zipper = new Zipper(file);
        return zipper.getEntryAsText("content.xml", "utf-8");
    }

    public String getOdtMetaXml() {
        Zipper zipper = new Zipper(file);
        return zipper.getEntryAsText("meta.xml", "utf-8");
    }

    public boolean checkFields(Collection<OdtField> fields) {
        return true;
    }
}
